﻿// Inner Fire 游戏引擎库
// Humanoid - 通用骨架动画
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 所属层级：应用层(4)
// 用于将一帧普通的动画保存为通用动画
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-05-17

#include "humanoid.h"
#include <game/animation/animation.h>
#include <game/animation/animator.h>
#include <graphics/imath.h>
#include <klog.h>

using namespace DirectX;

namespace ifire::game {

// 预定义常量
static const XMVECTOR EPSILON = XMVectorReplicate(0.01f);
static constexpr float MAX_FOOT_POSITION = 1.0e8f;

Humanoid::Humanoid(Animator* animator)
    : animator_(animator)
    , armature_(animator->GetArmature())
    , avatar_(armature_->GetAvatar())
    , use_foot_fix_(true)      // 修正脚部开关
    , use_direction_fix_(true) // 修复骨骼方向
    , use_scale_fix_(true)     // 使用Scale修正开关
    , use_root_motion_(true)   // 使用RootMotion开关
    , use_length_fix_(true)    // 使用骨骼长度修正开关
    , scale_vec_(SCALE1) {
}

void Humanoid::CalcAnimeFrame(Snapshot& desc) {

  // 通过Animator设置参数
  UseRootMotion(animator_->IsRootMotion());

  // 保存项目（以便不用每层都传递）
  clip_ = desc.clip;
  frame_ = desc.frame;

  // 得到A骨架
  anime_avatar_ = clip_->GetAvatar();
  anime_tree_ = anime_avatar_->GetNodeTree();
  auto armature_tree_ = armature_->GetNodeTree();

  // 开始动画
  /*KLOG("\n== %s -> %s Anime ==", anime_avatar_->name.c_str(),
      avatar_->name.c_str());*/

  // 设置矩阵以适应动画的大小
  auto_size(transforms_, anime_tree_->size());
  auto_size(b_parents_, anime_tree_->size());
  auto_size(animator_->parents_, anime_tree_->size());

  // 通过骨骼包围盒的大小，对项目进行修正
  if (use_scale_fix_ || use_foot_fix_) {
    //float a_width = anime_tree_->GetBoxWidth();
    //a_height = anime_tree_->GetBoxHeight();
    //float b_width = armature_->GetNodeTree()->GetBoxWidth();
    //b_height = armature_->GetNodeTree()->GetBoxHeight();

    //float scale_x = b_width / a_width;
    //float scale_y = b_height / a_height;
    //float scale_z = (scale_x + scale_y) * 0.5f; // 取平均值
    //scale_vec_ = XMVectorSet(scale_x, scale_y, scale_z, 0.0f);
    // 取骨骼长度的平均值进行计算（之前是使用包围盒，不如骨骼长度准确）
    auto hip_id = anime_avatar_->GetGroup()->hips_id;
    auto head_id = anime_avatar_->GetGroup()->head_id;
    auto a_hips = anime_tree_->Find(anime_avatar_->FindBone(hip_id));
    auto a_head = anime_tree_->Find(anime_avatar_->FindBone(head_id));
    auto b_hips = armature_tree_->Find(avatar_->FindBone(hip_id));
    auto b_head = armature_tree_->Find(avatar_->FindBone(head_id));
    float scale_head = b_head->tpose_total_length / a_head->tpose_total_length;
    float scale_hips = b_hips->tpose_total_length / a_hips->tpose_total_length;
    float avg = (scale_head + scale_hips) / 2.0f;
    a_height = avg;
    b_height = avg;
    scale_vec_ = XMVectorSet(avg, avg, avg, 0.0f);
  } else {
    scale_vec_ = SCALE1;
  }

  // 初始化Foot的修复
  a_foot_min = MAX_FOOT_POSITION;
  b_foot_min = MAX_FOOT_POSITION;

  // 计算动画
  CalcAnimation(desc);

  // 印射骨骼
  MapSkeleton(desc.transforms);

  // 修正方向
  if (use_direction_fix_) {
    FixDirection(desc.transforms);
  }

  // 修正脚部Position
  if (use_foot_fix_) {
    FixFoot(desc.transforms);
  }
}

IMat Humanoid::CalcNewTransform(ArmatureNode* node, ArmatureNode* b_node) {
  float scale_factor = XMVectorGetY(scale_vec_);

  // 得到t-pose，需要对这个t-pose进行缩放
  auto a_tpos = node->tpose_mat;
  if (use_scale_fix_) {
    a_tpos = imath::ScaleTranslation(a_tpos, scale_vec_);
  }

  // Plan-A：使用绝对矩阵进行计算
  // TIPS:
  // 这是最初能够得到一个理想的矩阵的计算。如果进行Position修正，会更加理想。
  auto diff = imath::MatAToB(a_tpos, a_global);

  // 计算出Position的相对位移（其实这里还有优化的空间，就是根据两个骨骼的总长进行比较）
  auto parent_position = imath::PickTranslate(a_parent);
  auto current_position = imath::PickTranslate(a_global);
  auto diff_position = current_position - parent_position;

  // 1. 分解矩阵获取旋转和缩放
  XMVECTOR rotationQuat = imath::PickRotation(b_parent);
  // 2. 构建逆旋转缩放矩阵(因为缩放是1，所以可以不计算）
  XMMATRIX inv_rot = imath::RotationMat(XMQuaternionInverse(rotationQuat));

  // 3. 变换位移向量(w分量设为0)
  XMVECTOR new_pos = XMVector3TransformNormal(diff_position, inv_rot);

  auto fix_id = avatar_->GetGroup()->hips_id;

  // Pass Hips，这个是为了防止Hips不一样时悬空（需要优化）
  bool is_length_fix = use_length_fix_;
  if (node->avatar_id == AVATAR_ROOT_ID) {
    is_length_fix = false;
  }

  // 这里应该有一个原项目的拉伸对比
  if (is_length_fix) {

    XMVECTOR len_vec = XMVector3Length(new_pos);
    XMVECTOR len_origin_vec = XMVectorReplicate(b_node->tpose_length);

    float old_len = XMVectorGetX(len_vec);
    float origin_a_len = node->tpose_length;
    float change_a_len = origin_a_len;
    float origin_b_len = b_node->tpose_length;
    float new_len = old_len;
    float len_scale = 1.0f;
    float change_scale_len = 1.0f;

    if (XMVector4Greater(len_vec, EPSILON) &&
        XMVector4Greater(len_origin_vec, EPSILON)) {
      // 计算出缩放的比率
      XMVECTOR scale = XMVectorDivide(len_origin_vec, len_vec);

      // TODO：对于腰部需要特殊处理，因为腰部在下蹲或越起的时候，长度就是有变化。所以需要一个长度对比。
      if (node->avatar_id == fix_id) {
        // 计算Scale的步骤。
        // 1、得到项目原来的t-pose的length，
        // 如果使用了缩放因子（一般都会ON），那么计算origin_a_len的变化。取Y轴的缩放。
        XMVECTOR origin_a_len =
            XMVectorReplicate(node->tpose_length * scale_factor);
        change_a_len = XMVectorGetX(origin_a_len);
        // 2、计算出一个缩放的比例，是当前的Position针对于原项目是变大还是变小
        XMVECTOR change_scale = XMVectorDivide(len_vec, origin_a_len);
        change_scale_len = XMVectorGetX(change_scale);

        // 需要两个缩放同时起作用
        new_pos = new_pos * scale * change_scale;

        new_len = imath::len(new_pos);
        len_scale = XMVectorGetX(scale);
      } else {
        // 如果不是Hips，只需要一个缩放即可
        new_pos = new_pos * scale;

        new_len = imath::len(new_pos);
        len_scale = XMVectorGetX(scale);
      }
    }
  }

  // 提取Relative
  XMMATRIX b_world = b_node->tpose_mat * diff;
  // 2. 计算 C = B × A⁻¹
  auto parent_to_world = imath::MatSubtract(b_world, b_parent);
  auto l_quat = XMQuaternionRotationMatrix(parent_to_world);

  // 重组相对矩阵
  return imath::BuildMat(new_pos, l_quat);
}

void Humanoid::CalcAnimation(Snapshot& desc) {

  const auto& anime_nodes = anime_tree_->Nodes();

  for (size_t i = 0; i < anime_nodes.size(); i++) {
    // 获得当前的节点
    const auto node = anime_nodes[i];

    auto node_mat = node->transformation;

    // 获得到B-Pose
    auto b_name = avatar_->FindBone(node->avatar_id);
    auto b_node = armature_->GetNodeTree()->Find(b_name);

    // 检查是否有动画。通过是否拥有Avatar，进行不同的处理
    auto channel = clip_->FindChannel(node->name);

    // 如果有动画，这个动画的位置就会替换Trans的变换
    bool has_anime = false;
    if (channel) {
      node_mat = clip_->CalcTransform(frame_, channel);
      has_anime = true;
    }

    // 如果有些动画需要进行翻转，在Root的节点进行翻转。
    if (node->avatar_id == AVATAR_ROOT_ID && clip_->IsFlipRotation()) {
      node_mat = node_mat * clip_->GetFlipRotation();
    }

    // 对大小进行修正（这个要在Root之前，因为获得的Root也是一个相对值）
    if (use_scale_fix_) {
      node_mat = imath::ScaleTranslation(node_mat, scale_vec_);
    }

    // 检查Root
    if (clip_->IsRootMotion() && animator_->IsRootMotion()) {
      if (node->IsRootMotionNode()) {
        desc.ExecuteRootMotion(node_mat);
      }
    } else {
      desc.PassRootMotion();
    }

    // 得到父级矩阵
    a_parent = node->parent ? animator_->parents_[node->parent->index] : ID_MAT;
    b_parent = node->parent ? b_parents_[node->parent->index] : ID_MAT;

    // 获得当前这个节点的变换
    a_global = node_mat * a_parent;
    // 这是用来保存每一个项目的相对，如果没有相应的骨骼，则直接继承上一个父级（如果遇到骨骼丢失的情况，也许会出错，但没有办法）
    b_global = b_parent;

    // 获得Chara的T-Pose的替换矩阵(传说中的矩阵Legend-Matrix)
    if (b_node) {
      auto l_mat = CalcNewTransform(node, b_node);
      transforms_[node->index] = l_mat;
      b_global = l_mat * b_parent;

      // 如果是需要计算FootIK的项目，则保存数据
      if (use_foot_fix_) {
        SaveFootPosition(node->avatar_id, imath::PickTranslate(a_global),
            imath::PickTranslate(b_global));
      }
    }

    // 设置父级
    animator_->parents_[node->index] = a_global;
    b_parents_[node->index] = b_global;
  }
}

void Humanoid::MapSkeleton(MatrixList& bones) {
  // --------------------------------------------------------------------------
  // 计算动画B2
  // --------------------------------------------------------------------------
  /*KLOG("\n== %s -> %s Map ==", anime_avatar_->name.c_str(),
      avatar_->name.c_str());*/

  const auto& chara_nodes = armature_->GetNodeTree()->Nodes();

  for (size_t i = 0; i < chara_nodes.size(); i++) {
    const auto node = chara_nodes[i];

    // 得到父级矩阵
    b_parent = node->parent ? animator_->parents_[node->parent->index] : ID_MAT;

    // 获得此节点相对于父节点的变换
    auto trans = node->transformation;

    // 检查是骨骼是否为Avatar骨骼，如果不是，则使用默认处理
    if (node->avatar_id == AVATAR_NONE_ID) {
      b_global = trans * b_parent;
    } else {
      // 这里的查找是相当消耗资源的，以后想办法简化。
      auto a_name = anime_avatar_->FindBone(node->avatar_id);
      auto a_node = anime_tree_->Find(a_name);
      if (a_node) {
        b_global = transforms_[a_node->index] * b_parent;
      } else {
        // 这是一个SafeGuard。按理来说是不会执行到这儿的。
        b_global = trans * b_parent;
      }
    }

    // 当前节点如果有骨骼
    if (node->bone_id != -1) {
      bones[node->bone_id] = b_global;
    }

    // 保存父级
    animator_->parents_[node->index] = b_global;
  }
}

void Humanoid::FixDirection(MatrixList& bones) {
  const auto& chara_nodes = armature_->GetNodeTree()->Nodes();
  auto avatar_group = armature_->GetAvatar()->GetGroup();

  for (size_t i = 0; i < chara_nodes.size(); i++) {
    const auto node = chara_nodes[i];

    // 如果没有骨骼，则不做处理。ROOT骨骼很特殊，也不做处理。
    if (node->bone_id == -1 || node->avatar_id == -1 ||
        node->avatar_id == AVATAR_ROOT_ID) {
      continue;
    }

    if (!avatar_group->Get(node->avatar_id).fix_direction) {
      continue;
    }

    // 这里就是最有意思的一点了，当前骨骼的照向，需要向其子骨骼对齐。
    // 但如果一个骨骼有多个子节点，这是计算不了的。所以暂时忽略。
    // 但有可能这个骨骼有一些假骨骼，所以必须找到一根在Avatar中的
    ArmatureNode* child = nullptr;
    if (node->children.size() == 0) {
      continue;
    }
    for (auto e : node->children) {
      if (e->avatar_id != AVATAR_NONE_ID && e->bone_id != -1) {
        child = e;
        break;
      }
    }
    if (child == nullptr) {
      continue;
    }

    // 当前Bones中存放的就是最终矩阵，相比t-pose也是最终矩阵
    auto a_bone = bones[node->bone_id];
    auto a_pose = node->tpose_mat;

    auto a_name = node->name.c_str();
    auto b_name = child->name.c_str();

    // 得到子节点的参数
    auto b_bone = bones[child->bone_id];
    auto b_pose = child->tpose_mat;

    // 如果骨骼没有长度，则不进行计算
    if (child->tpose_length < 0.01f) {
      KLOG("Pass %s -> %s", a_name, b_name);
      continue;
    }

    // 在原始的骨骼中，骨骼拥有自已的旋转，还有与下一个骨骼的朝向
    auto origin_rot = imath::PickRotation(a_pose);
    auto origin_dir = imath::CalculateDirection(a_pose, b_pose);
    auto current_dir = imath::CalculateDirection(a_bone, b_bone);
    // 如果没有长度
    if (XMVectorGetX(XMVector3LengthSq(current_dir)) < FLT_EPSILON){
      continue;
    }

    // 2. 计算从origin_dir旋转到current_dir的四元数
    auto angle = XMVector3AngleBetweenVectors(origin_dir, current_dir);
    XMVECTOR rotation = XMQuaternionRotationAxis(
        XMVector3Cross(origin_dir, current_dir), // 旋转轴
        XMVectorGetX(angle)                      // 旋转角度
    );
    // 3. 将新旋转应用到原始旋转上
    XMVECTOR current_rot = XMQuaternionMultiply(origin_rot, rotation);

    // 将旋转替换
    bones[node->bone_id] = imath::SetRotation(a_bone, current_rot);
  }
}

void Humanoid::FixFoot(MatrixList& bones) {
  // 如果项目陷到地底，则设置为0
  if (a_foot_min < 0.0f) {
    a_foot_min;
  }

  // 检查这个高度是自已的Height的百分之多少
  // 因为前面已经放大过了，所以这里需要除了放大系数
  float y_scale = XMVectorGetY(scale_vec_);
  float jump_percent = (a_foot_min / y_scale) / a_height;

  // 得到B应该Jump的高度
  float jump_height = jump_percent * b_height;

  float offset = jump_height - b_foot_min;

  // 如果会让项目陷到地下，则恢复
  if (b_foot_min + offset < 0.0f) {
    offset += -(b_foot_min + offset);
  }

  // 对每一个矩阵的坐标进行修复
  for (auto& m : bones) {
    m.r[3].m128_f32[1] += offset;
  }
}

void Humanoid::SaveFootPosition(int avatar_id, FVec a, FVec b) {
  if (anime_avatar_->GetGroup()->IsFootFix(avatar_id)) {
    a_foot_min = imath::Min(a_foot_min, a.m128_f32[1]);
    b_foot_min = imath::Min(b_foot_min, b.m128_f32[1]);
  }
}

} // namespace ifire::game